﻿using System;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using Action = System.Action;
using Should;

namespace MugenInjectionTest
{

    #region Test helpers

    public delegate void RefDelegate(ref object obj, out int i);

    public delegate int FuncValueDelegateRef(ref int obj);

#if NOSKIPVISIBILITY
    public class ExtendedClass : Exception
#else
    internal class ExtendedClass : Exception
#endif
    {
        private static readonly object InitObj;

        static ExtendedClass()
        {
            InitObj = new object();
        }

        public ExtendedClass(string arg1, object arg2, int arg3)
        {
            Arg1 = arg1;
            Arg2 = arg2;
            Arg3 = arg3;
        }

        public string Arg1 { get; set; }
        public object Arg2 { get; set; }
        public int Arg3 { get; set; }
    }


    public struct ActivatorTestStruct
    {
        public string Arg1;
        public object Arg2;
        public int Arg3;

        public ActivatorTestStruct(string arg1, object arg2, int arg3)
        {
            Arg1 = arg1;
            Arg2 = arg2;
            Arg3 = arg3;
        }
    }

    public class InvokeMethodClass
    {
        public const string StValue = "))))))))))))))";
        public const int IntValue = 1;
        public static bool StaticInvoked;

        public bool InternalInvoked;
        public bool PrivateInvoked;
        public bool ProtectedInvoked;
        public bool PublickInvoked;

        public static void StaticMethod()
        {
            StaticInvoked = true;
        }

        public static string StaticEcho(string st)
        {
            return st;
        }

        public static int StaticWithValue(int value)
        {
            return value;
        }

        public static int StaticWithValueRef(ref int value)
        {
            value = IntValue;
            return value;
        }

        public void PublicMethod()
        {
            PublickInvoked = true;
        }

#if NOSKIPVISIBILITY
        public void InternalMethod()
#else
        internal void InternalMethod()
#endif
        {
            InternalInvoked = true;
        }


#if NOSKIPVISIBILITY
        public void ProtectedMethod()
#else
        protected void ProtectedMethod()
#endif
        {
            ProtectedInvoked = true;
        }

#if NOSKIPVISIBILITY
        public void PrivateMethod()
#else
        private void PrivateMethod()
#endif

        {
            PrivateInvoked = true;
        }

        public void RefParameterMethod(ref string st, ref int i)
        {
            st = StValue;
            i = IntValue;
        }

        public string RefCombineParameterMethod(ref string st, string arg2, ref int i)
        {
            st = StValue;
            i = IntValue;
            return arg2;
        }
    }

    public class FieldAndPropertyTest
    {
        public static object StaticField;
        public object PublicField;

        public object PublicProperty { get; set; }

        public static object StaticProperty { get; set; }
    }

    public struct FieldAndPropertyStructTest
    {
        public static object StaticField;
        public object PublicField;

        public object PublicProperty { get; set; }

        public static object StaticProperty { get; set; }
    }

    public class FieldAndPropertyValueTypeTest
    {
        public static int StaticField;
        public int PublicField;

        public int PublicProperty { get; set; }

        public static int StaticProperty { get; set; }
    }

    public struct FieldAndPropertyValueTypeStructTest
    {
        public static int StaticField;
        public int PublicField;

        public int PublicProperty { get; set; }

        public static int StaticProperty { get; set; }
    }

    public class WeakEventClass
    {
        public const int Value = 10000;
        public static readonly object StaticObject = new object();

        public void RefDelegate(ref object obj, out int i)
        {
            obj = StaticObject;
            i = Value;
        }

        public object FuncDelegate(object obj1)
        {
            return StaticObject;
        }

        public static object StaticFuncDelegate(object obj)
        {
            return StaticObject;
        }

        public int FuncValueDelegate(int value)
        {
            return value;
        }

        public int FuncValueDelegateRef(ref int value)
        {
            value = Value;
            return value;
        }
    }

    public class WeakEventClassTarget
    {
        public event RefDelegate RefDelegate;

        public void OnRefDelegate(ref object obj, out int i)
        {
            i = 0;
            RefDelegate handler = RefDelegate;
            if (handler != null)
                handler(ref obj, out i);
        }

        public event System.Func<object, object> EventFunc;

        public object OnEventFunc(object arg)
        {
            System.Func<object, object> handler = EventFunc;
            if (handler != null)
                return handler(arg);
            return null;
        }

        public event System.Func<int, int> FuncValueDelegate;

        public int OnFuncValueDelegate(int arg)
        {
            System.Func<int, int> handler = FuncValueDelegate;
            if (handler != null)
                return handler(arg);
            return default(int);
        }

        public event FuncValueDelegateRef FuncValueDelegateRef;

        public int OnFuncValueDelegateRef(ref int obj)
        {
            FuncValueDelegateRef handler = FuncValueDelegateRef;
            if (handler != null)
                return handler(ref obj);
            return default(int);
        }

        public Delegate[] GetRefDelagates()
        {
            if (RefDelegate == null)
                return null;
            return RefDelegate.GetInvocationList();
        }

        public Delegate[] GetEventFuncDelegates()
        {
            if (EventFunc == null)
                return null;
            return EventFunc.GetInvocationList();
        }

        public Delegate[] GetFuncValueDelegateRef()
        {
            if (FuncValueDelegateRef == null)
                return null;
            return FuncValueDelegateRef.GetInvocationList();
        }

        public Delegate[] GetFuncValueDelegate()
        {
            if (FuncValueDelegate == null)
                return null;
            return FuncValueDelegate.GetInvocationList();
        }
    }

    public class TestReflectionClass
    {
        public string Field;

        public string MethodValue;

        public TestReflectionClass(string st)
        {
            St = st;
        }

        public string St { get; set; }

        public void Method(string st)
        {
            MethodValue = st;
        }
    }

    public struct TestReflectionStruct
    {
        public string Field;

        public string MethodValue;

        public TestReflectionStruct(string st)
            : this()
        {
            St = st;
        }

        public string St { get; set; }

        public void Method(string st)
        {
            MethodValue = st;
        }
    }

    #endregion

    public abstract class ReflectionProviderTestBase
    {
        [TestMethod]
        public void CreateInstanceTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //Test private class
            ConstructorInfo constructorInfo = typeof(PrivateClass).GetConstructor(new Type[0]);
            var instance = ReflectionExtension.CreateInstance(constructorInfo) as PrivateClass;
            Assert.IsNotNull(instance);

            //Error parameters
            constructorInfo =
                typeof(ExtendedClass).GetConstructor(new[] { typeof(string), typeof(object), typeof(int) });
            TestUtils.Catch<Exception>(() => ReflectionExtension.CreateInstance(constructorInfo));


            string arg1 = "Test";
            object arg2 = Guid.NewGuid();
            int arg3 = 3;

            //Error parameter
            TestUtils.Catch<InvalidCastException>(
                () => ReflectionExtension.CreateInstance(constructorInfo, arg3, arg2, arg1));

            //Success
            var extendedClass =
                ReflectionExtension.CreateInstance(constructorInfo, arg1, arg2, arg3) as ExtendedClass;
            Assert.IsNotNull(extendedClass);
            Assert.AreEqual(extendedClass.Arg1, arg1);
            Assert.AreEqual(extendedClass.Arg2, arg2);
            Assert.AreEqual(extendedClass.Arg3, arg3);

            //Struct
            constructorInfo =
                typeof(ActivatorTestStruct).GetConstructor(new[] { typeof(string), typeof(object), typeof(int) });
            var testStruct =
                (ActivatorTestStruct)ReflectionExtension.CreateInstance(constructorInfo, arg1, arg2, arg3);
            Assert.IsNotNull(testStruct);
            Assert.AreEqual(testStruct.Arg1, arg1);
            Assert.AreEqual(testStruct.Arg2, arg2);
            Assert.AreEqual(testStruct.Arg3, arg3);

            //Static constructor
#if NETFX_CORE
            constructorInfo =
                typeof(ExtendedClass).GetTypeInfo().DeclaredConstructors.First(info => info.IsStatic);
#else
            constructorInfo =
                typeof(ExtendedClass).GetConstructors(BindingFlags.Static | BindingFlags.Default |
                                                       BindingFlags.NonPublic).First();
#endif

            TestUtils.Catch<ArgumentException>(
                () => ReflectionExtension.CreateInstance(constructorInfo));
        }

        [TestMethod]
        public void GetCreateInstanceDelegateTest()
        {
            IReflectionAccessProvider provider = GetProvider();
            InjectorUtils.ReflectionAccessProvider = provider;
            //Test private class
            ConstructorInfo constructorInfo = typeof(PrivateClass).GetConstructor(new Type[0]);
            ActivatorDelegate instanceDelegate1 = provider.GetActivatorDelegate(constructorInfo);
            ActivatorDelegate instanceDelegate2 = provider.GetActivatorDelegate(constructorInfo);
            Assert.AreEqual(instanceDelegate1, instanceDelegate2);
            var privateClass = instanceDelegate1(new object[0]) as PrivateClass;
            Assert.IsNotNull(privateClass);

            constructorInfo =
                typeof(ExtendedClass).GetConstructor(new[] { typeof(string), typeof(object), typeof(int) });
            instanceDelegate1 = provider.GetActivatorDelegate(constructorInfo);
            instanceDelegate2 = provider.GetActivatorDelegate(constructorInfo);

            string arg1 = "Test";
            object arg2 = Guid.NewGuid();
            int arg3 = 3;
            var extendedClass =
                ReflectionExtension.CreateInstance(constructorInfo, arg1, arg2, arg3) as ExtendedClass;
            Assert.IsNotNull(extendedClass);
            Assert.AreEqual(extendedClass.Arg1, arg1);
            Assert.AreEqual(extendedClass.Arg2, arg2);
            Assert.AreEqual(extendedClass.Arg3, arg3);

            //Static constructor
#if NETFX_CORE
            constructorInfo =
                typeof(ExtendedClass).GetTypeInfo().DeclaredConstructors.First(info => info.IsStatic);
#else
            constructorInfo =
                typeof(ExtendedClass).GetConstructors(BindingFlags.Static | BindingFlags.Default |
                                                       BindingFlags.NonPublic).First();

#endif
            TestUtils.Catch<ArgumentException>(
                () => ReflectionExtension.CreateInstance(constructorInfo));
        }

        [TestMethod]
        public void GetMethodInvokeDelegateTest()
        {
            IReflectionAccessProvider provider = GetProvider();
            Assert.AreEqual(
                provider.GetInvokeMethodDelegate<object>(typeof(InvokeMethodClass).GetMethod("StaticEcho")),
                provider.GetInvokeMethodDelegate<object>(typeof(InvokeMethodClass).GetMethod("StaticEcho")));
            Assert.AreEqual(
                provider.GetInvokeMethodDelegate<object>(typeof(InvokeMethodClass).GetMethod("PublicMethod")),
                provider.GetInvokeMethodDelegate<object>(typeof(InvokeMethodClass).GetMethod("PublicMethod")));
        }

        [TestMethod]
        public void InvokeMethodTest()
        {
            InvokeMethodClass.StaticInvoked = false;
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //static method
            Assert.IsFalse(InvokeMethodClass.StaticInvoked);
            Assert.IsNull(ReflectionExtension.InvokeFast<object>(typeof(InvokeMethodClass).GetMethod("StaticMethod"),
                                                                 null));
            Assert.IsTrue(InvokeMethodClass.StaticInvoked);

            const string st = "echo";
            Assert.AreEqual(
                ReflectionExtension.InvokeFast<object>(typeof(InvokeMethodClass).GetMethod("StaticEcho"), null, st), st);

            const int value = 11919;
            Assert.AreEqual(
                ReflectionExtension.InvokeFast<object>(typeof(InvokeMethodClass).GetMethod("StaticWithValue"), null,
                                                       value), value);

            Assert.AreEqual(
                ReflectionExtension.InvokeFast<object>(typeof(InvokeMethodClass).GetMethod("StaticWithValueRef"), null,
                                                       value), InvokeMethodClass.IntValue);

#if !NETFX_CORE
            const BindingFlags bindingFlags = BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Static |
                                              BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public;
#endif


            var target = new InvokeMethodClass();
            //public method
            Assert.IsFalse(target.PublickInvoked);
            Assert.IsNull(ReflectionExtension.InvokeFast(typeof(InvokeMethodClass).GetMethod("PublicMethod"),
                                                         target));
            Assert.IsTrue(target.PublickInvoked);

            //internal method
            Assert.IsFalse(target.InternalInvoked);

#if NETFX_CORE
            var methodInfo = typeof(InvokeMethodClass).GetMethod("InternalMethod");
#else
            MethodInfo methodInfo = typeof(InvokeMethodClass).GetMethod("InternalMethod", bindingFlags);
#endif
            Assert.IsNull(ReflectionExtension.InvokeFast(methodInfo, target));
            Assert.IsTrue(target.InternalInvoked);

            //protected method
            Assert.IsFalse(target.ProtectedInvoked);
#if NETFX_CORE
            methodInfo = typeof(InvokeMethodClass).GetMethod("ProtectedMethod");
#else
            methodInfo = typeof(InvokeMethodClass).GetMethod("ProtectedMethod", bindingFlags);
#endif
            Assert.IsNull(ReflectionExtension.InvokeFast(methodInfo, target));
            Assert.IsTrue(target.ProtectedInvoked);

            //private method
            Assert.IsFalse(target.PrivateInvoked);
#if NETFX_CORE
            methodInfo = typeof(InvokeMethodClass).GetMethod("PrivateMethod");
#else
            methodInfo = typeof(InvokeMethodClass).GetMethod("PrivateMethod", bindingFlags);
#endif
            Assert.IsNull(ReflectionExtension.InvokeFast(methodInfo, target));
            Assert.IsTrue(target.PrivateInvoked);

            //ref parameter
            string arg1 = "TestSt";
            int i = -1;
            var objects = new object[] { arg1, i };
            Assert.IsNull(ReflectionExtension.InvokeFast(typeof(InvokeMethodClass).GetMethod("RefParameterMethod"),
                                                         target, objects));
            Assert.AreEqual(objects[0], InvokeMethodClass.StValue);
            Assert.AreEqual(objects[1], InvokeMethodClass.IntValue);

            objects = new object[] { arg1, arg1, i };
            Assert.AreEqual(
                ReflectionExtension.InvokeFast(typeof(InvokeMethodClass).GetMethod("RefCombineParameterMethod"),
                                               target,
                                               objects), arg1);
            Assert.AreEqual(objects[0], InvokeMethodClass.StValue);
            Assert.AreEqual(objects[1], arg1);
            Assert.AreEqual(objects[2], InvokeMethodClass.IntValue);


            //Empty target
            TestUtils.Catch<ArgumentException>(
                () => ReflectionExtension.InvokeFast(typeof(InvokeMethodClass).GetMethod("PublicMethod"), null));

            //Parameter mismatch
            TestUtils.Catch<Exception>(
                () =>
                ReflectionExtension.InvokeFast(typeof(InvokeMethodClass).GetMethod("RefCombineParameterMethod"),
                                               target));
        }

        [TestMethod]
        public void SetGetPropertyTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //non-static
            //class
            PropertyInfo propertyInfo = typeof(FieldAndPropertyTest).GetProperty("PublicProperty");
            var value = new object();
            var target = new FieldAndPropertyTest();

            object property = ReflectionExtension.GetValueFast(propertyInfo, target);
            Assert.IsNull(target.PublicProperty);
            Assert.IsNull(property);
            ReflectionExtension.SetValueFast(propertyInfo, ref target, value);
            Assert.AreEqual(target.PublicProperty, value);
            property = ReflectionExtension.GetValueFast(propertyInfo, target);
            Assert.AreEqual(property, value);

            //struct
            propertyInfo = typeof(FieldAndPropertyStructTest).GetProperty("PublicProperty");
            var targetSt = new FieldAndPropertyStructTest();

            property = ReflectionExtension.GetValueFast(propertyInfo, targetSt);
            Assert.IsNull(targetSt.PublicProperty);
            Assert.IsNull(property);
            ReflectionExtension.SetValueFast(propertyInfo, ref targetSt, value);
            Assert.AreEqual(targetSt.PublicProperty, value);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(propertyInfo, targetSt);
            Assert.AreEqual(property, value);

            //static property class
#if !NETFX_CORE
            const BindingFlags flags =
                BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetProperty |
                BindingFlags.SetProperty;
#endif

#if NETFX_CORE
            propertyInfo = typeof(FieldAndPropertyTest).GetProperty("StaticProperty");
#else
            propertyInfo = typeof(FieldAndPropertyTest).GetProperty("StaticProperty", flags);
#endif
            FieldAndPropertyTest.StaticProperty = null;
            target = null;
            property = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(propertyInfo, target);
            Assert.IsNull(FieldAndPropertyTest.StaticProperty);
            Assert.IsNull(property);
            ReflectionExtension.SetValueFast(propertyInfo, ref target, value);
            Assert.AreEqual(FieldAndPropertyTest.StaticProperty, value);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(propertyInfo, target);
            Assert.AreEqual(property, value);

            //static property struct
#if NETFX_CORE
            propertyInfo = typeof(FieldAndPropertyStructTest).GetProperty("StaticProperty");
#else
            propertyInfo = typeof(FieldAndPropertyStructTest).GetProperty("StaticProperty", flags);
#endif
            FieldAndPropertyStructTest.StaticProperty = null;
            targetSt = default(FieldAndPropertyStructTest);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(propertyInfo, targetSt);
            Assert.IsNull(FieldAndPropertyStructTest.StaticProperty);
            Assert.IsNull(property);
            ReflectionExtension.SetValueFast(propertyInfo, ref targetSt, value);
            Assert.AreEqual(FieldAndPropertyStructTest.StaticProperty, value);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(propertyInfo, targetSt);
            Assert.AreEqual(property, value);
        }

        [TestMethod]
        public void SetGetValueTypePropertyTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //non-static
            //class
            PropertyInfo propertyInfo = typeof(FieldAndPropertyValueTypeTest).GetProperty("PublicProperty");
            const int value = 10;
            var target = new FieldAndPropertyValueTypeTest();

            object property = ReflectionExtension.GetValueFast(propertyInfo, target);
            target.PublicProperty.ShouldEqual(0);
            property.ShouldEqual(0);

            ReflectionExtension.SetValueFast(propertyInfo, ref target, value);
            target.PublicProperty.ShouldEqual(value);
            property = ReflectionExtension.GetValueFast(propertyInfo, target);
            property.ShouldEqual(value);

            //struct
            propertyInfo = typeof(FieldAndPropertyValueTypeStructTest).GetProperty("PublicProperty");
            var targetSt = new FieldAndPropertyValueTypeStructTest();

            property = ReflectionExtension.GetValueFast(propertyInfo, targetSt);
            targetSt.PublicProperty.ShouldEqual(0);
            property.ShouldEqual(0);
            ReflectionExtension.SetValueFast(propertyInfo, ref targetSt, value);
            targetSt.PublicProperty.ShouldEqual(value);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(propertyInfo, targetSt);
            property.ShouldEqual(value);

            //static property class
#if !NETFX_CORE
            const BindingFlags flags =
                BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetProperty |
                BindingFlags.SetProperty;
#endif

#if NETFX_CORE
            propertyInfo = typeof(FieldAndPropertyValueTypeTest).GetProperty("StaticProperty");
#else
            propertyInfo = typeof(FieldAndPropertyValueTypeTest).GetProperty("StaticProperty", flags);
#endif
            FieldAndPropertyValueTypeTest.StaticProperty = 0;
            target = null;
            property = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(propertyInfo, target);
            FieldAndPropertyValueTypeTest.StaticProperty.ShouldEqual(0);
            property.ShouldEqual(0);
            ReflectionExtension.SetValueFast(propertyInfo, ref target, value);
            FieldAndPropertyValueTypeTest.StaticProperty.ShouldEqual(value);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(propertyInfo, target);
            property.ShouldEqual(value);

            //static property struct
#if NETFX_CORE
            propertyInfo = typeof(FieldAndPropertyValueTypeStructTest).GetProperty("StaticProperty");
#else
            propertyInfo = typeof(FieldAndPropertyValueTypeStructTest).GetProperty("StaticProperty", flags);
#endif
            FieldAndPropertyValueTypeStructTest.StaticProperty = 0;
            targetSt = default(FieldAndPropertyValueTypeStructTest);
            property = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(propertyInfo, targetSt);
            property.ShouldEqual(0);
            ReflectionExtension.SetValueFast(propertyInfo, ref targetSt, value);
            FieldAndPropertyValueTypeStructTest.StaticProperty.ShouldEqual(value);

            property = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(propertyInfo, targetSt);
            property.ShouldEqual(value);
        }

        [TestMethod]
        public void SetGetFieldTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //non-static
            //class
            FieldInfo fieldInfo = typeof(FieldAndPropertyTest).GetField("PublicField");
            var value = new object();
            var target = new FieldAndPropertyTest();

            object field = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(fieldInfo, target);
            Assert.IsNull(target.PublicField);
            Assert.IsNull(field);
            ReflectionExtension.SetValueFast(fieldInfo, ref target, value);
            Assert.AreEqual(target.PublicField, value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(fieldInfo, target);
            Assert.AreEqual(field, value);

            //struct
            fieldInfo = typeof(FieldAndPropertyStructTest).GetField("PublicField");
            var targetSt = new FieldAndPropertyStructTest();

            field = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(fieldInfo, targetSt);
            Assert.IsNull(targetSt.PublicField);
            Assert.IsNull(field);
            ReflectionExtension.SetValueFast(fieldInfo, ref targetSt, value);
            Assert.AreEqual(targetSt.PublicField, value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(fieldInfo, targetSt);
            Assert.AreEqual(field, value);

            //static property class
#if !NETFX_CORE
            const BindingFlags flags =
                BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField |
                BindingFlags.SetField;
#endif

#if NETFX_CORE
            fieldInfo = typeof(FieldAndPropertyTest).GetField("StaticField");
#else
            fieldInfo = typeof(FieldAndPropertyTest).GetField("StaticField", flags);
#endif
            FieldAndPropertyTest.StaticField = null;
            target = null;
            field = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(fieldInfo, target);
            Assert.IsNull(FieldAndPropertyTest.StaticField);
            Assert.IsNull(field);
            ReflectionExtension.SetValueFast(fieldInfo, ref target, value);
            Assert.AreEqual(FieldAndPropertyTest.StaticField, value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyTest, object>(fieldInfo, target);
            Assert.AreEqual(field, value);

            //static property struct
#if NETFX_CORE
            fieldInfo = typeof(FieldAndPropertyStructTest).GetField("StaticField");
#else
            fieldInfo = typeof(FieldAndPropertyStructTest).GetField("StaticField", flags);
#endif
            FieldAndPropertyStructTest.StaticField = null;
            targetSt = default(FieldAndPropertyStructTest);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(fieldInfo, targetSt);
            Assert.IsNull(FieldAndPropertyStructTest.StaticField);
            Assert.IsNull(field);
            ReflectionExtension.SetValueFast(fieldInfo, ref targetSt, value);
            Assert.AreEqual(FieldAndPropertyStructTest.StaticField, value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyStructTest, object>(fieldInfo, targetSt);
            Assert.AreEqual(field, value);
        }

        [TestMethod]
        public void SetGetValueTypeFieldTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            //non-static
            //class
            FieldInfo fieldInfo = typeof(FieldAndPropertyValueTypeTest).GetField("PublicField");
            const int value = 10;
            var target = new FieldAndPropertyValueTypeTest();

            object field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(fieldInfo, target);
            field.ShouldEqual(0);

            ReflectionExtension.SetValueFast(fieldInfo, ref target, value);
            target.PublicField.ShouldEqual(value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(fieldInfo, target);
            field.ShouldEqual(value);

            //struct
            fieldInfo = typeof(FieldAndPropertyValueTypeStructTest).GetField("PublicField");
            var targetSt = new FieldAndPropertyValueTypeStructTest();

            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(fieldInfo, targetSt);
            field.ShouldEqual(0);
            ReflectionExtension.SetValueFast(fieldInfo, ref targetSt, value);
            targetSt.PublicField.ShouldEqual(value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(fieldInfo, targetSt);
            field.ShouldEqual(value);

            //static property class
#if !NETFX_CORE
            const BindingFlags flags =
                BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField |
                BindingFlags.SetField;
#endif

#if NETFX_CORE
            fieldInfo = typeof(FieldAndPropertyValueTypeTest).GetField("StaticField");
#else
            fieldInfo = typeof(FieldAndPropertyValueTypeTest).GetField("StaticField", flags);
#endif
            FieldAndPropertyValueTypeTest.StaticField = 0;
            target = null;
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(fieldInfo, target);
            field.ShouldEqual(0);
            ReflectionExtension.SetValueFast(fieldInfo, ref target, value);
            FieldAndPropertyValueTypeTest.StaticField.ShouldEqual(value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeTest, object>(fieldInfo, target);
            field.ShouldEqual(value);

            //static property struct
#if NETFX_CORE
            fieldInfo = typeof(FieldAndPropertyValueTypeStructTest).GetField("StaticField");
#else
            fieldInfo = typeof(FieldAndPropertyValueTypeStructTest).GetField("StaticField", flags);
#endif
            FieldAndPropertyValueTypeStructTest.StaticField = 0;
            targetSt = default(FieldAndPropertyValueTypeStructTest);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(fieldInfo, targetSt);
            field.ShouldEqual(0);

            ReflectionExtension.SetValueFast(fieldInfo, ref targetSt, value);
            FieldAndPropertyValueTypeTest.StaticField.ShouldEqual(value);
            field = ReflectionExtension.GetValueFast<FieldAndPropertyValueTypeStructTest, object>(fieldInfo, targetSt);
            field.ShouldEqual(value);
        }

        [TestMethod]
        public void WeakEventTestWithoutUnsubcribe()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<object, object>(target.FuncDelegate));
            eventClass.RefDelegate += ReflectionExtension.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate));
            eventClass.FuncValueDelegate +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<int, int>(target.FuncValueDelegate));
            eventClass.FuncValueDelegateRef +=
                ReflectionExtension.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef));

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            int value = 999;
            int i;
            object obj = null;
            Assert.AreEqual(eventClass.OnEventFunc(new object()), WeakEventClass.StaticObject);
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), value);

            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), WeakEventClass.Value);
            Assert.AreEqual(value, WeakEventClass.Value);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.AreEqual(obj, WeakEventClass.StaticObject);
            Assert.AreEqual(i, WeakEventClass.Value);

            //clear target
            target = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            obj = null;
            Assert.IsNull(eventClass.OnEventFunc(new object()));
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), default(int));

            int value1 = value;
            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), default(int));
            Assert.AreEqual(value, value1);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.IsNull(obj);
            Assert.AreEqual(i, 0);

            Assert.AreEqual(eventClass.GetEventFuncDelegates().Length, 1);
            Assert.AreEqual(eventClass.GetRefDelagates().Length, 1);
            Assert.AreEqual(eventClass.GetFuncValueDelegate().Length, 1);
            Assert.AreEqual(eventClass.GetFuncValueDelegateRef().Length, 1);
        }

        [TestMethod]
        public void WeakEventTestWithUnsubcribe()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<object, object>(target.FuncDelegate),
                                                          func => eventClass.EventFunc -= func);
            eventClass.RefDelegate += ReflectionExtension.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate),
                                                                                @delegate =>
                                                                                eventClass.RefDelegate -= @delegate);
            eventClass.FuncValueDelegate +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<int, int>(target.FuncValueDelegate),
                                                          func => eventClass.FuncValueDelegate -= func);
            eventClass.FuncValueDelegateRef +=
                ReflectionExtension.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef),
                                                          @ref => eventClass.FuncValueDelegateRef -= @ref);

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            int value = 999;
            int i;
            object obj = null;
            Assert.AreEqual(eventClass.OnEventFunc(new object()), WeakEventClass.StaticObject);
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), value);

            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), WeakEventClass.Value);
            Assert.AreEqual(value, WeakEventClass.Value);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.AreEqual(obj, WeakEventClass.StaticObject);
            Assert.AreEqual(i, WeakEventClass.Value);

            //clear target
            target = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            obj = null;
            Assert.IsNull(eventClass.OnEventFunc(new object()));
            Assert.AreEqual(eventClass.OnFuncValueDelegate(value), default(int));

            int value1 = value;
            Assert.AreEqual(eventClass.OnFuncValueDelegateRef(ref value), default(int));
            Assert.AreEqual(value, value1);

            eventClass.OnRefDelegate(ref obj, out i);
            Assert.IsNull(obj);
            Assert.AreEqual(i, 0);

            Assert.IsNull(eventClass.GetEventFuncDelegates());
            Assert.IsNull(eventClass.GetRefDelagates());
            Assert.IsNull(eventClass.GetFuncValueDelegate());
            Assert.IsNull(eventClass.GetFuncValueDelegateRef());
        }

        [TestMethod]
        public void FindWeakDelegateByOriginalDelegateTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            var eventClass = new WeakEventClassTarget();
            var target = new WeakEventClass();
            eventClass.EventFunc +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<object, object>(target.FuncDelegate));
            eventClass.RefDelegate += ReflectionExtension.ConvertToWeakDelegate(new RefDelegate(target.RefDelegate));

            eventClass.FuncValueDelegate +=
                ReflectionExtension.ConvertToWeakDelegate(new System.Func<int, int>(target.FuncValueDelegate));
            eventClass.FuncValueDelegateRef +=
                ReflectionExtension.ConvertToWeakDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef));

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            var func = ReflectionExtension.FindWeakDelegateByOriginalDelegate(new System.Func<object, object>(target.FuncDelegate), eventClass.GetEventFuncDelegates());
            var refDelegate = ReflectionExtension.FindWeakDelegateByOriginalDelegate(new RefDelegate(target.RefDelegate), eventClass.GetRefDelagates());
            var funcValue = ReflectionExtension.FindWeakDelegateByOriginalDelegate(new System.Func<int, int>(target.FuncValueDelegate), eventClass.GetFuncValueDelegate());
            var funcValueRef = ReflectionExtension.FindWeakDelegateByOriginalDelegate(new FuncValueDelegateRef(target.FuncValueDelegateRef), eventClass.GetFuncValueDelegateRef());

            Assert.IsNotNull(func);
            Assert.IsNotNull(refDelegate);
            Assert.IsNotNull(funcValue);
            Assert.IsNotNull(funcValueRef);

            eventClass.EventFunc -= func;
            eventClass.RefDelegate -= refDelegate;
            eventClass.FuncValueDelegate -= funcValue;
            eventClass.FuncValueDelegateRef -= funcValueRef;

            Assert.IsNull(eventClass.GetEventFuncDelegates());
            Assert.IsNull(eventClass.GetRefDelagates());
            Assert.IsNull(eventClass.GetFuncValueDelegate());
            Assert.IsNull(eventClass.GetFuncValueDelegateRef());

        }

        [TestMethod]
        public void CommonClassTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            const string value = "value";
            ConstructorInfo constructor = typeof(TestReflectionClass).GetConstructor(new[] { typeof(string) });
            FieldInfo field = typeof(TestReflectionClass).GetField("Field");
            PropertyInfo property = typeof(TestReflectionClass).GetProperty("St");
            MethodInfo method = typeof(TestReflectionClass).GetMethod("Method");

            var reflectionClass = (TestReflectionClass)ReflectionExtension.CreateInstance(constructor, value);
            Assert.AreEqual(reflectionClass.St, value);
            reflectionClass.St = null;

            //Set get fields no ref
            string fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionClass.Field);

            ReflectionExtension.SetValueFast(field, reflectionClass, value);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.AreEqual(fieldValue, value);
            Assert.AreEqual(reflectionClass.Field, value);

            ReflectionExtension.SetValueFast(field, reflectionClass, null);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionClass.Field);

            //Set get fields ref
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionClass.Field);

            ReflectionExtension.SetValueFast(field, ref reflectionClass, value);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.AreEqual(fieldValue, value);
            Assert.AreEqual(reflectionClass.Field, value);

            ReflectionExtension.SetValueFast(field, ref reflectionClass, null);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(field, reflectionClass);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionClass.Field);

            //Set get property no ref
            string propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property,
                                                                                                 reflectionClass);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionClass.St);

            ReflectionExtension.SetValueFast(property, reflectionClass, value);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property, reflectionClass);
            Assert.AreEqual(propertyValue, value);
            Assert.AreEqual(reflectionClass.St, value);

            ReflectionExtension.SetValueFast(property, reflectionClass, null);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property, reflectionClass);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionClass.St);

            //Set get property ref
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property, reflectionClass);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionClass.St);

            ReflectionExtension.SetValueFast(property, ref reflectionClass, value);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property, reflectionClass);
            Assert.AreEqual(propertyValue, value);
            Assert.AreEqual(reflectionClass.St, value);

            ReflectionExtension.SetValueFast(property, ref reflectionClass, null);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionClass, string>(property, reflectionClass);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionClass.St);

            //Call method no ref
            Assert.IsNull(reflectionClass.MethodValue);
            ReflectionExtension.InvokeFast(method, reflectionClass, value);
            Assert.AreEqual(reflectionClass.MethodValue, value);

            ReflectionExtension.InvokeFast(method, reflectionClass, new object[] { null });
            Assert.IsNull(reflectionClass.MethodValue);

            //Call method ref
            Assert.IsNull(reflectionClass.MethodValue);
            ReflectionExtension.InvokeFast(method, ref reflectionClass, value);
            Assert.AreEqual(reflectionClass.MethodValue, value);

            ReflectionExtension.InvokeFast(method, ref reflectionClass, new object[] { null });
            Assert.IsNull(reflectionClass.MethodValue);
        }

        [TestMethod]
        public void CommonStructTest()
        {
            InjectorUtils.ReflectionAccessProvider = GetProvider();
            const string value = "value";
            ConstructorInfo constructor = typeof(TestReflectionStruct).GetConstructor(new[] { typeof(string) });
            FieldInfo field = typeof(TestReflectionStruct).GetField("Field");
            PropertyInfo property = typeof(TestReflectionStruct).GetProperty("St");
            MethodInfo method = typeof(TestReflectionStruct).GetMethod("Method");

            var reflectionStruct = (TestReflectionStruct)ReflectionExtension.CreateInstance(constructor, value);
            Assert.AreEqual(reflectionStruct.St, value);
            reflectionStruct.St = null;

            //Set get fields ref
            string fieldValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(field, reflectionStruct);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionStruct.Field);

            ReflectionExtension.SetValueFast(field, ref reflectionStruct, value);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(field, reflectionStruct);
            Assert.AreEqual(fieldValue, value);
            Assert.AreEqual(reflectionStruct.Field, value);

            ReflectionExtension.SetValueFast(field, ref reflectionStruct, null);
            fieldValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(field, reflectionStruct);
            Assert.IsNull(fieldValue);
            Assert.IsNull(reflectionStruct.Field);

            //Set get property ref
            string propertyValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(property,
                                                                                                  reflectionStruct);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionStruct.St);

            ReflectionExtension.SetValueFast(property, ref reflectionStruct, value);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(property, reflectionStruct);
            Assert.AreEqual(propertyValue, value);
            Assert.AreEqual(reflectionStruct.St, value);

            ReflectionExtension.SetValueFast(property, ref reflectionStruct, null);
            propertyValue = ReflectionExtension.GetValueFast<TestReflectionStruct, string>(property, reflectionStruct);
            Assert.IsNull(propertyValue);
            Assert.IsNull(reflectionStruct.St);

            //Call method ref
            Assert.IsNull(reflectionStruct.MethodValue);
            ReflectionExtension.InvokeFast(method, ref reflectionStruct, value);
            Assert.AreEqual(reflectionStruct.MethodValue, value);

            ReflectionExtension.InvokeFast(method, ref reflectionStruct, new object[] { null });
            Assert.IsNull(reflectionStruct.MethodValue);
        }

        [TestMethod]
        public void ConvertDelegateVoidTest()
        {
            var reflectionAccessProvider = GetProvider();
            bool isInvoked = false;
            var action = (Action)reflectionAccessProvider.ConvertToProxyDelegate(objects => isInvoked = true, typeof(Action));
            action();
            Assert.IsTrue(isInvoked);
        }

        [TestMethod]
        public void ConvertDelegateInputTest()
        {
            var obj = new InvokeMethodClass();
            var reflectionAccessProvider = GetProvider();
            bool isInvoked = false;
            var action = (Action<InvokeMethodClass>)reflectionAccessProvider.ConvertToProxyDelegate(objects =>
                                                                                                        {
                                                                                                            Assert.AreEqual(objects[0], obj);
                                                                                                            isInvoked = true;
                                                                                                            return null;
                                                                                                        }, typeof(Action<InvokeMethodClass>));
            action(obj);
            Assert.IsTrue(isInvoked);
        }

        [TestMethod]
        public void ConvertDelegateReturnAndInputTest()
        {
            var obj = new InvokeMethodClass();
            IReflectionAccessProvider reflectionAccessProvider = GetProvider();
            bool isInvoked = false;
            var action = (System.Func<InvokeMethodClass, IReflectionAccessProvider>)reflectionAccessProvider.ConvertToProxyDelegate(objects =>
            {
                Assert.AreEqual(objects[0], obj);
                isInvoked = true;
                return reflectionAccessProvider;
            }, typeof(System.Func<InvokeMethodClass, IReflectionAccessProvider>));
            var accessProvider = action(obj);
            Assert.AreEqual(accessProvider, reflectionAccessProvider);
            Assert.IsTrue(isInvoked);
        }

        [TestMethod]
        public void ConvertDelegateRefVoidTest()
        {
            var reflectionAccessProvider = GetProvider();
            bool isInvoked = false;
            var action = (RefDelegate)reflectionAccessProvider
                                           .ConvertToProxyDelegate(objects =>
                                                                       {
                                                                           Assert.AreEqual(objects[0],
                                                                                           reflectionAccessProvider);
                                                                           Assert.AreEqual(objects[1], default(int));
                                                                           isInvoked = true;
                                                                           return null;
                                                                       }, typeof(RefDelegate));
            object obj = reflectionAccessProvider;
            int value;
            action(ref obj, out value);
            Assert.AreEqual(obj, reflectionAccessProvider);
            Assert.AreEqual(value, default(int));
            Assert.IsTrue(isInvoked);
            isInvoked = false;


            var newObj = new WeakEventClass();
            action = (RefDelegate)reflectionAccessProvider
                                       .ConvertToProxyDelegate(objects =>
                                                                   {
                                                                       Assert.AreEqual(objects[0],
                                                                                           reflectionAccessProvider);
                                                                       Assert.AreEqual(objects[1], default(int));

                                                                       objects[0] = newObj;
                                                                       objects[1] = int.MaxValue;
                                                                       isInvoked = true;
                                                                       return null;
                                                                   }, typeof(RefDelegate));
            obj = reflectionAccessProvider;
            action(ref obj, out value);
            Assert.AreEqual(obj, newObj);
            Assert.AreEqual(value, int.MaxValue);
            Assert.IsTrue(isInvoked);
        }

        [TestMethod]
        public void ConvertDelegateRefNotVoidTest()
        {
            var reflectionAccessProvider = GetProvider();
            bool isInvoked = false;
            var action = (FuncValueDelegateRef)reflectionAccessProvider
                                                    .ConvertToProxyDelegate(objects =>
                                                                                {
                                                                                    Assert.AreEqual(objects[0],
                                                                                                    int.MinValue);
                                                                                    isInvoked = true;
                                                                                    return int.MaxValue;
                                                                                }, typeof(FuncValueDelegateRef));
            int value = int.MinValue;
            var i = action(ref value);
            Assert.AreEqual(i, int.MaxValue);
            Assert.IsTrue(isInvoked);
        }

        protected abstract IReflectionAccessProvider GetProvider();

        #region Nested type: PrivateClass

#if NOSKIPVISIBILITY
        public class PrivateClass
#else
        private class PrivateClass
#endif
        {
        }

        #endregion
    }

#if !NETFX_CORE
    [TestClass]
    public sealed class EmitReflectionProviderTest : ReflectionProviderTestBase
    {
        #region Overrides of ReflectionProviderTestBase

        protected override IReflectionAccessProvider GetProvider()
        {
            return new EmitReflectionAccessProvider();
        }

        #endregion
    }
#endif


#if FULLEXPRESSION
    [TestClass]
    public sealed class ExpressionReflectionProviderTest : ReflectionProviderTestBase
    {
    #region Overrides of ReflectionProviderTestBase

        protected override IReflectionAccessProvider GetProvider()
        {
            return new ExpressionReflectionAccessProvider();
        }

    #endregion
    }
#endif

}